package com.adamfass.tractorbeam;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jme.scene.shape.Torus;
import com.jme.scene.state.MaterialState;
import com.jme.system.DisplaySystem;
import com.jmex.physics.DynamicPhysicsNode;
import com.jmex.physics.PhysicsSpace;

public abstract class PhysicsGameObject implements GameEntity, Blocker {
  protected final DynamicPhysicsNode physicsNode;
  private final Map<Object, ColorRGBA> targetterToColor;
  private final Node targetFeedbackGeometry;
  
  private boolean isDeleted = false;
  
  protected PhysicsGameObject(PhysicsSpace physicsSpace) {
    physicsNode = physicsSpace.createDynamicNode();
    targetterToColor = new HashMap<Object, ColorRGBA>();
    targetFeedbackGeometry = new Node("target feedback node");
    physicsNode.attachChild(targetFeedbackGeometry);
  }
  
  public void beforeUpdate() {
    physicsNode.clearForce();
  }
  
  public void update() {
    // Keep from rotating or moving along the z axis.
    physicsNode.clearTorque();
    physicsNode.setAngularVelocity(Vector3f.ZERO);
    physicsNode.setLocalRotation(new Quaternion());
    final Vector3f translation = physicsNode.getLocalTranslation();
    translation.setZ(0);
    physicsNode.setLocalTranslation(translation);
    physicsNode.updateGeometricState(0, true);
  }
  
  public void cleanUp() {
    physicsNode.delete();
  }
  
  public void addForce(Vector3f force) {
    physicsNode.addForce(force);
  }
  
  public final Vector3f getPosition() {
    physicsNode.updateWorldVectors();
    return physicsNode.getWorldTranslation();
  }
  
  public final Vector3f getVelocity() {
    return physicsNode.getLinearVelocity(new Vector3f());
  }
  
  public final void addTargetter(Object targetter, ColorRGBA color) {
    assert targetter != null;
    assert color != null;
    if (targetterToColor.put(targetter, color) == null) {
      rebuildTargetFeedbackGeometry(
          targetFeedbackGeometry, getTargetterColors());
    }
  }
  
  public final void removeTargetter(Object targetter) {
    assert targetter != null;
    final ColorRGBA color = targetterToColor.remove(targetter);
    assert color != null;
    rebuildTargetFeedbackGeometry(
        targetFeedbackGeometry, getTargetterColors());
  }
  
  private final Iterable<ColorRGBA> getTargetterColors() {
    ArrayList<ColorRGBA> colors = new ArrayList<ColorRGBA>();
    for (Map.Entry<Object, ColorRGBA> entry : targetterToColor.entrySet()) {
      colors.add(entry.getValue());
    }
    return colors;
  }
  
  protected void rebuildTargetFeedbackGeometry(
      final Node targetFeedbackGeometry,
      final Iterable<ColorRGBA> targetterColors) {
    // getChildren gives us the real list maintained by the Node, not a copy,
    // so we must make a copy in order to iterate through it and remove them.
    final List<Spatial> children = targetFeedbackGeometry.getChildren(); 
    if (children != null) {
      final ArrayList<Spatial> childrenForRemoval = 
        new ArrayList<Spatial>(children);
      for (Spatial child : childrenForRemoval) {
        child.removeFromParent();
      }
    }
    
    float radius = 1.2f;
    for (ColorRGBA targetterColor : targetterColors) {
      final Torus torus = new Torus("target feedback", 20, 10, 0.1f, radius);
      final MaterialState materialState = 
        DisplaySystem.getDisplaySystem().getRenderer().createMaterialState();
      materialState.setEnabled(true);
      materialState.setDiffuse(targetterColor);
      materialState.setAmbient(targetterColor);
      torus.setRenderState(materialState);
      targetFeedbackGeometry.attachChild(torus);      
      radius += 0.2f;
    }
  }
  
  public Spatial getBlockerSpatial() {
    return physicsNode;
  }
  
  public void markAsDeleted() {
    assert !isDeleted;
    isDeleted = true;
  }
  
  public boolean isDeleted() {
    return isDeleted;
  }
}
